package com.congee02.spring.context;

import com.congee02.spring.annoation.Autowired;
import com.congee02.spring.annoation.Component;
import com.congee02.spring.annoation.ComponentScan;
import com.congee02.spring.annoation.Scope;
import com.congee02.spring.aware.BeanNameAware;
import com.congee02.spring.aware.base.Aware;
import com.congee02.spring.beans.BeanDefinition;
import com.congee02.spring.beans.BeanPostProcessor;
import com.congee02.spring.beans.InitializingBean;
import com.congee02.spring.beans.Scopes;
import com.congee02.utils.FileUtils;
import com.congee02.utils.ReflectUtils;
import com.congee02.utils.TypeUtils;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author congee(congee02 @ 163.com)
 * @date 1/27/2023 9:18 AM
 */
public class CustomApplicationContext {

    private List<Class> typeWhiteList = List.of(Aware.class, BeanPostProcessor.class);

    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    private ConcurrentHashMap<String, Object> nameSingletonObjects = new ConcurrentHashMap<>();
    private ConcurrentHashMap<Class, Object> typeSingletonObjects = new ConcurrentHashMap<>();

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public CustomApplicationContext(Class configClass) {

        // 解析配置类
        /* ComponentScan -> 扫描路径 -> 扫描 */
        ComponentScan componentScan = configClass.isAnnotationPresent(ComponentScan.class) ?
                        (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class) : null;

        String scanPackage = componentScan.value();
        String relativeScanPath = scanPackage.replace(".", "/");

        ClassLoader loader = this.getClass().getClassLoader();
        URL resource = loader.getResource(relativeScanPath);
        String classPath = resource.getFile();
        File classDirectory = new File(classPath);

        if ((! classDirectory.exists()) || (! classDirectory.isDirectory())) {
            throw new RuntimeException("ComponentScan 参数必须为存在的包");
        }

        /* 获取字节码文件 */
        List<File> files = FileUtils.ergodic(classDirectory);
        for (File f : files) {
            if (! f.getAbsolutePath().endsWith(".class")) {
                continue;
            }
            String classFileAbsolutePath = f.getAbsolutePath();

            String fullyQualifiedNameSuffix = classFileAbsolutePath
                    .replace(classDirectory.getAbsolutePath(), "")
                    .replace("\\", ".");
            fullyQualifiedNameSuffix = fullyQualifiedNameSuffix.substring(0, fullyQualifiedNameSuffix.indexOf(".class"));
            String fullyQualifiedName = scanPackage + fullyQualifiedNameSuffix;

            try {
                Class<?> clazz = loader.loadClass(fullyQualifiedName);

                Component component = clazz.isAnnotationPresent(Component.class) ?
                        clazz.getDeclaredAnnotation(Component.class) : null;
                if (component == null) {
                    continue;
                }

                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    BeanPostProcessor beanPostProcessor =
                            (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                    beanPostProcessorList.add(beanPostProcessor);
                }

                Scope scope = clazz.isAnnotationPresent(Scope.class) ?
                        clazz.getDeclaredAnnotation(Scope.class) : null;
                Scopes scopeVal = scope == null ? Scopes.SINGLETON : scope.value();
                BeanDefinition definition = new BeanDefinition();

                definition.setType(clazz);
                definition.setScope(scopeVal);

                String beanName = component.value();
                beanName = "".equals(beanName) ? Introspector.decapitalize(clazz.getSimpleName()) : beanName;

                beanDefinitionMap.put(beanName, definition);

            } catch (ClassNotFoundException | NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        }

        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition definition = beanDefinitionMap.get(beanName);
            Object bean = null;
            if (Scopes.SINGLETON.equals(definition.getScope())) {
                System.out.println(definition);
                bean = createBean(beanName, definition);
                nameSingletonObjects.put(beanName, bean);
            }
            /*==============================*/
            Class clazz = definition.getType();
            for (Class<?> i : clazz.getInterfaces()) {

                boolean flag = false;
                for (Class whiteClazz : typeWhiteList) {

                    flag = TypeUtils.isSubClass(i, whiteClazz);

                    if (flag) break;
                }

                if (typeSingletonObjects.get(i) != null) {
                    throw new RuntimeException("Get by class: Ambiguous bean ," + i.getName());
                }
                if (! flag) {
                    typeSingletonObjects.put(i, bean);
                }
            }
            List<Class> allSuperClasses = ReflectUtils.getAllSuperClasses(clazz);


            for (Class superClass : allSuperClasses) {

                if (typeSingletonObjects.get(superClass) != null) {
                    throw new RuntimeException("Get by class: Ambiguous bean, " + superClass.getName());
                }

                typeSingletonObjects.put(superClass, bean);
            }
        }
    }

    public Object createBean(String beanName, BeanDefinition definition) {

        Class clazz = definition.getType();

        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // 依赖注入
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)) {
                    Object injectVal = null;
                    f.setAccessible(true);
                    try {
                        injectVal = getBean(f.getType());
                    } catch (NullPointerException e) {
                        if (f.getAnnotation(Autowired.class).value()) {
                            throw new NullPointerException();
                        }
                    }
                    f.setAccessible(true);
                    f.set(instance, injectVal);
                }
            }

            // Aware 回调
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // 初始化前操作
            for (BeanPostProcessor processor : beanPostProcessorList) {
                instance = processor.postProcessBeforeInitialization(instance, beanName);
            }

            // bean 初始化
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            // BeanPostProcessor ; bean 初始化前&后执行的操作
            for (BeanPostProcessor processor: beanPostProcessorList) {
                instance = processor.postProcessAfterInitialization(instance, beanName);
            }


            return instance;
        } catch (InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public <T> T getBean(Class<T> clazz) {

        T bean = (T) typeSingletonObjects.get(clazz);

        if (bean == null) {
            throw new NullPointerException();
        }

        return bean;
    }

    public Object getBean(String beanName) {

        BeanDefinition definition = beanDefinitionMap.get(beanName);

        if (definition == null) {
            throw new NullPointerException();
        }

        Scopes scope = definition.getScope();
        if (Scopes.SINGLETON.equals(scope)) {
            Object bean = nameSingletonObjects.get(beanName);
            if (bean == null) {
                Object o = createBean(beanName, definition);
                nameSingletonObjects.put(beanName, bean);
                bean = o;
            }
            return bean;
        }

        return createBean(beanName, definition);
    }

}
